package appTool

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"gitee.com/kinwyb/appTools/common"
	"gitee.com/kinwyb/appTools/log"
	"gitee.com/kinwyb/conv"
	"gitee.com/kinwyb/godb"
	"github.com/emicklei/go-restful"
	"github.com/go-openapi/spec"
	jsoniter "github.com/json-iterator/go"
	"github.com/kinwyb/go-restful-openapi"
	"github.com/modern-go/reflect2"
	"github.com/rcrowley/go-metrics"
	"github.com/rcrowley/go-metrics/exp"
	"go.elastic.co/apm/module/apmlogrus"
	"go.elastic.co/apm/module/apmrestful"
	"io/ioutil"
	"math"
	"net"
	"net/http"
	"net/http/pprof"
	"os"
	"reflect"
	"strings"
	"time"
	"unsafe"
)

// RestfulHttp 应用http配置
type RestfulHttp struct {
	isRun            bool
	listenAddr       string                 `description:"监听端口"`
	port             int                    `description:"端口"`
	enableMetrics    bool                   `description:"开启metrics"`
	enablePprof      bool                   `description:"开启pprof"`
	metricsFlushTime time.Duration          `description:"metrics时间"`
	httpContainer    *restful.Container     `description:"http对象"`
	httpServer       *http.Server           `description:"http服务"`
	swaggerTag       []spec.Tag             `description:"swagger tag"`
	useLog           bool                   `description:"是否启用日志"`
	apmTrace         restful.FilterFunction `description:"apm追踪过滤"`
	listenFile       *os.File               //监听文件
}

func (a *RestfulHttp) EnableMetrics(flushTime time.Duration) *RestfulHttp {
	a.enableMetrics = true
	a.metricsFlushTime = flushTime
	startMetrics(flushTime)
	a.initContainer()
	handler := exp.ExpHandler(metricsRegistry)
	a.httpContainer.ServeMux.Handle("/metrics", handler)
	log.AppTool.Infof("开启Metrics => %s:%d/debug/metrics", a.listenAddr, a.port)
	return a
}

func (a *RestfulHttp) DisenableMetrics() {
	a.enableMetrics = false
	stopMetrics()
}

// ListenAddr 获取监听地址
func (a *RestfulHttp) ListenAddr() string {
	return a.listenAddr
}

// Port 获取监听端口
func (a *RestfulHttp) Port() int {
	return a.port
}

func (a *RestfulHttp) EnablePprof() *RestfulHttp {
	if a.enablePprof {
		return a
	}
	a.enablePprof = true
	a.initContainer()
	a.httpContainer.ServeMux.Handle("/debug/pprof/", pprofHandler("/debug/pprof/"))
	log.AppTool.Infof("开启pprof => %s:%d/debug/pprof/", a.listenAddr, a.port)
	return a
}

func (a *RestfulHttp) DisenablePprof() *RestfulHttp {
	a.enablePprof = false
	return a
}

func (a *RestfulHttp) Shutdown(ctx context.Context) {
	a.isRun = false
	if a.httpServer != nil {
		err := a.httpServer.Shutdown(ctx)
		if err != nil {
			log.AppTool.Errorf("http服务关闭异常:%s", err.Error())
		}
		a.httpServer = nil
	}
	stopMetrics()
}

func (a *RestfulHttp) initContainer() {
	if a.httpContainer == nil {
		a.httpContainer = restful.NewContainer()
		a.httpContainer.Filter(a.apmTraceFilter)
		a.httpContainer.Filter(a.filterfunc)
		restful.RegisterEntityAccessor(restful.MIME_JSON, &entityJSONAccess{
			ContentType: restful.MIME_JSON + ";charset=UTF-8",
		})
		restful.DefaultResponseContentType(restful.MIME_JSON)
	}
}

func (a *RestfulHttp) Run() {
	// 允许关闭后重新开启
	if a.httpServer != nil {
		c, _ := context.WithTimeout(context.Background(), 30*time.Second)
		a.httpServer.Shutdown(c)
	}
	a.httpServer = &http.Server{
		Addr: fmt.Sprintf("%s:%d", a.listenAddr, a.port),
	}
	a.initContainer()
	a.isRun = true
	if a.enableMetrics {
		startMetrics(a.metricsFlushTime)
	}
	if common.TraceType == common.TraceTypeAPM {
		a.apmTrace = apmrestful.Filter()
	}
	a.httpServer.Handler = a.httpContainer.ServeMux
	var listener net.Listener
	var err error
	if a.listenFile != nil {
		listener, err = net.FileListener(a.listenFile)
		if err != nil {
			log.AppTool.WithError(err).Error("http listen file error")
			listener = nil
		}
	}
	if listener == nil {
		listener, err = net.Listen("tcp", a.httpServer.Addr)
		if err != nil {
			log.AppTool.WithError(err).Fatal("http监听异常")
		}
		if tcp, ok := listener.(*net.TCPListener); ok {
			a.listenFile, _ = tcp.File()
		}
	} else {
		log.AppTool.Info("http加载指定监听文件")
	}
	log.AppTool.Errorf("http监听错误：%s", a.httpServer.Serve(listener))
}

func (a *RestfulHttp) Swagger(cfg *RestfulSwaggerConfig) {
	a.initContainer()
	cfg.httpContainer = a.httpContainer
	cfg.Tags = append(cfg.Tags, a.swaggerTag...)
	config := restfulspec.Config{
		WebServices:                   a.httpContainer.RegisteredWebServices(), // you control what services are visible
		APIPath:                       "/apidocs.json",
		BasePath:                      cfg.BasePath,
		Host:                          cfg.Host,
		Schemes:                       cfg.Schemes,
		PostBuildSwaggerObjectHandler: cfg.enrichSwaggerObject,
	}
	a.httpContainer.Add(restfulspec.NewOpenAPIService(config))
}

func (a *RestfulHttp) SwaggerTag(tag spec.Tag) {
	a.swaggerTag = append(a.swaggerTag, tag)
}

// RestfulContainer 获取restful.Container
func (a *RestfulHttp) RestfulContainer() *restful.Container {
	a.initContainer()
	return a.httpContainer
}

// AddService 增加web服务
func (a *RestfulHttp) AddService(ws *restful.WebService) {
	a.initContainer()
	a.httpContainer.Add(ws)
}

// UseLog 增加web服务
func (a *RestfulHttp) UseLog(enableLog bool) {
	a.useLog = enableLog
}

func (a *RestfulHttp) filterfunc(request *restful.Request, response *restful.Response, chain *restful.FilterChain) {
	ctx := restfulHttpContext(request, response)
	request.SetAttribute("ctx", ctx)
	inBody, err := ioutil.ReadAll(request.Request.Body)
	if err != nil {
		response.WriteError(400, err)
		return
	}
	request.Request.Body.Close()
	request.SetAttribute("body", inBody)
	bf := bytes.NewBuffer(inBody)
	// 重新赋值body方便解析
	request.Request.Body = http.MaxBytesReader(response.ResponseWriter, ioutil.NopCloser(bf), int64(len(inBody)+10))
	startTime := time.Now()
	chain.ProcessFilter(request, response)
	if metricsRegistry != nil {
		metrics.GetOrRegisterTimer(request.Request.URL.Path, metricsRegistry).UpdateSince(startTime)
		metrics.GetOrRegisterTimer(allRequestMetrics, metricsRegistry).UpdateSince(startTime) //总数请求
	}
	if tracingspan := ctx.TracingSpan(); tracingspan != nil {
		tracingspan.InjectHttpHeader(response.ResponseWriter.Header())
	}
	ctx.Finish()
	if a.useLog {
		// 记录请求日志
		useTime := (time.Now().UnixNano() - startTime.UnixNano()) / int64(time.Millisecond)
		// 这里也可以通过X-Forwarded-For请求头的第一个值作为用户的ip
		// 但是要注意的是这两个请求头代表的ip都有可能是伪造的
		ip := request.HeaderParameter("X-Real-IP")
		if ip == "" {
			// 当请求头不存在即不存在代理时直接获取ip
			ip = strings.Split(request.Request.RemoteAddr, ":")[0]
		}
		lg := log.AppTool
		if common.TraceType == common.TraceTypeAPM {
			trace := ctx.TracingSpan()
			if trace != nil {
				traceContextFields := apmlogrus.TraceContext(trace.Context(context.Background()))
				lg = lg.WithFields(traceContextFields)
			}
		}
		lg.WithField("url", request.Request.RequestURI).
			WithField("time", useTime).
			WithField("status", response.StatusCode()).
			Infof("%s %s %s %s %d [%s] %s %dms",
				request.Request.Proto,
				request.Request.Method,
				request.Request.Host,
				request.Request.RequestURI,
				response.StatusCode(),
				request.Request.UserAgent(),
				ip,
				useTime,
			)
	}
}

func (a *RestfulHttp) apmTraceFilter(request *restful.Request, response *restful.Response, chain *restful.FilterChain) {
	if a.apmTrace != nil {
		a.apmTrace(request, response, chain)
	} else {
		chain.ProcessFilter(request, response)
	}
}

// RestfulRequest 解析请求结果
func RestfulRequest(request *restful.Request, response *restful.Response) *RestfulResponse {
	ret := &RestfulResponse{resp: response, req: request}
	ctx := request.Attribute("ctx")
	if ctx == nil {
		ret.ctx = restfulHttpContext(request, response)
	} else {
		ret.ctx = ctx.(Context)
	}
	return ret
}

// 返回结构
type restfulResponseObj struct {
	Code   int64         `description:"错误编码" json:"code"`
	ErrMsg string        `description:"错误描述" json:"errmsg,omitempty"`
	Data   interface{}   `description:"返回结果内容" json:"data"`
	Page   *godb.PageObj `description:"分页信息" json:"page,omitempty"`
}

// SwaggerApiResponseStruct 返回结构
type SwaggerApiResponseStruct struct {
	Code   int64  `description:"错误编码" json:"code"`
	ErrMsg string `description:"错误描述" json:"errmsg,omitempty"`
	Err    string `description:"错误内容" json:"err,omitempty"`
}

// SwaggerBoolResponseStruct 返回结构,用于swagger文档解析
type SwaggerBoolResponseStruct struct {
	Code   int64  `description:"错误编码" json:"code"`
	ErrMsg string `description:"错误描述" json:"errmsg,omitempty"`
	Data   bool   `description:"是否成功" json:"data"`
}

// SwaggerIntegerResponseStruct 返回结构,用于swagger文档解析
type SwaggerIntegerResponseStruct struct {
	Code   int64  `description:"错误编码" json:"code"`
	ErrMsg string `description:"错误描述" json:"errmsg,omitempty"`
	Data   int64  `description:"是否成功" json:"data"`
}

type RestfulHttpContextParse = func(ctx AppContext, request *restful.Request) Context

var restfulHttpContextParseFun RestfulHttpContextParse

// RegisterRestfulHttpContextParse 注册RestfulHttp => context 解析函数
func RegisterRestfulHttpContextParse(fun RestfulHttpContextParse) {
	restfulHttpContextParseFun = fun
}

// RestfulResponse 返回对象包装
type RestfulResponse struct {
	ctx  Context
	resp *restful.Response
	req  *restful.Request
	page *godb.PageObj
}

type Error interface {
	error
	Code() int64 //获取错误码
}

// 初始化restful请求context
func restfulHttpContext(request *restful.Request, resposne *restful.Response) Context {
	ctx := NewContextWithTracing(
		common.NewTracingSpanExtractHttpRequest(request.Request))
	if ctx.TracingSpan() != nil { //将追踪信息放入返回的header中
		ctx.TracingSpan().InjectHttpHeader(resposne.Header())
	}
	if restfulHttpContextParseFun != nil {
		return restfulHttpContextParseFun(ctx, request)
	}
	return ctx
}

func (r *RestfulResponse) Context() Context {
	if r.ctx == nil {
		r.ctx = restfulHttpContext(r.req, r.resp)
	}
	return r.ctx
}

func (r *RestfulResponse) Error(err error) {
	if v, ok := err.(Error); ok {
		r.responseData(&restfulResponseObj{
			Code:   v.Code(),
			ErrMsg: v.Error(),
		})
	} else {
		r.responseData(&restfulResponseObj{
			Code:   500,
			ErrMsg: err.Error(),
		})
	}
}

// 直接输入结果
func (r *RestfulResponse) Write(data string) {
	r.resp.Write([]byte(data))
}

// ErrorMsg 返回错误消息
func (r *RestfulResponse) ErrorMsg(errMsg string) {
	r.responseData(&restfulResponseObj{
		Code:   500,
		ErrMsg: errMsg,
	})
}

// 返回结果处理
func (r *RestfulResponse) responseData(data *restfulResponseObj) {
	// 请求失败,记录请求日志
	if data.Code != 0 {
		lg := log.AppTool
		if common.TraceType == common.TraceTypeAPM {
			trace := r.Context().TracingSpan()
			if trace != nil {
				traceContextFields := apmlogrus.TraceContext(trace.Context(context.Background()))
				lg = lg.WithFields(traceContextFields)
			}
		}
		lg.WithField("url", r.req.Request.RequestURI).
			WithField("body", common.ToString(r.req.Attribute("body"))).
			WithField("type", r.req.HeaderParameter("Content-Type")).
			WithField("code", data.Code).
			WithField("error", errors.New(data.ErrMsg)).
			WithField("method", r.req.Request.Method).
			Error("请求失败")
	}
	if data.Page == nil && r.req.HeaderParameter("no-code") == "true" {
		// 只返回结果内容，有分页信息的不能直接返回
		var resultString string
		if data.Code != 0 {
			resultString = data.ErrMsg
		} else {
			if result, ok := data.Data.(string); ok {
				resultString = result
			} else {
				resultString = string(common.JsonData(data.Data))
			}
		}
		r.resp.Write([]byte(resultString))
		return
	}
	r.resp.WriteEntity(data)
}

// Succ 返回成功
func (r *RestfulResponse) Succ(data interface{}) {
	if data == nil {
		data = "执行成功"
	}
	r.responseData(&restfulResponseObj{
		Code: 0,
		Data: data,
		Page: r.page,
	})
}

func (r *RestfulResponse) Page() *godb.PageObj {
	if r.page == nil {
		r.page = &godb.PageObj{}
	}
	r.page.Page = conv.ToIntDefault(r.req.QueryParameter("page"), 1)
	r.page.Rows = conv.ToIntDefault(r.req.QueryParameter("pageSize"), 20)
	return r.page
}

func (r *RestfulResponse) ParamMissing(param string) {
	r.responseData(&restfulResponseObj{
		Code:   501,
		ErrMsg: param + "参数缺失",
	})
}

func (r *RestfulResponse) ParamDecode(v interface{}) error {
	var body []byte
	if v, ok := r.req.Attribute("body").([]byte); ok {
		body = v
	}
	if len(body) > 0 {
		e := json.Unmarshal(body, &v)
		if e == nil {
			return nil
		}
		typeName := reflect.TypeOf(v).String()
		lg := log.AppTool
		if common.TraceType == common.TraceTypeAPM {
			trace := r.Context().TracingSpan()
			if trace != nil {
				traceContextFields := apmlogrus.TraceContext(trace.Context(context.Background()))
				lg = lg.WithFields(traceContextFields)
			}
		}
		lg.WithField("info", fmt.Sprintf("[%s]请求内容:%s\n请求类型:%s", typeName, string(body),
			r.req.HeaderParameter("content-type"))).WithError(e).Error("参数解析错误")
		r.responseData(&restfulResponseObj{
			Code:   502,
			ErrMsg: "参数解析错误:" + e.Error(),
		})
	} else {
		r.responseData(&restfulResponseObj{
			Code:   502,
			ErrMsg: "body请求参数缺少",
		})
	}
	return errors.New("body请求参数缺少")
}

// RestfulApiResultDo 基本API返回结构
func RestfulApiResultDo(builder *restful.RouteBuilder) {
	builder.Returns(http.StatusOK, "请求成功", SwaggerApiResponseStruct{})
}

const SwaggerSecurityDefinitionKey = "SwaggerSecurityDefinition"

type RestfulSwaggerConfig struct {
	Title               string `description:"标题"`
	Description         string `description:"描述"`
	Version             string `description:"版本"`
	Host                string `description:"host"`
	Tags                []spec.Tag
	Schemes             []string
	BasePath            string `description:"基本路由地址"`
	securityDefinitions map[string]*spec.SecurityScheme
	httpContainer       *restful.Container
}

func (c *RestfulSwaggerConfig) SecurityDefinition(name string, security *spec.SecurityScheme) {
	if c.securityDefinitions == nil {
		c.securityDefinitions = map[string]*spec.SecurityScheme{}
	}
	c.securityDefinitions[name] = security
}

func (c *RestfulSwaggerConfig) enrichSwaggerObject(swo *spec.Swagger) {
	swo.Info = &spec.Info{
		InfoProps: spec.InfoProps{
			Title:       c.Title,
			Description: c.Description,
			Version:     c.Version,
		},
	}
	swo.Tags = c.Tags
	// setup security definitions
	swo.SecurityDefinitions = c.securityDefinitions
	// default security definitions
	if len(swo.SecurityDefinitions) < 1 {
		swo.SecurityDefinitions = map[string]*spec.SecurityScheme{
			"token":           spec.APIKeyAuth("token", "header"),
			serverModeMetaKey: spec.APIKeyAuth(serverModeMetaKey, "header"),
		}
	}
	// loop through all registerd web services
	for _, ws := range c.httpContainer.RegisteredWebServices() {
		for _, route := range ws.Routes() {
			// grab route metadata for a SecurityDefinition
			secdefn, ok := route.Metadata[SwaggerSecurityDefinitionKey]
			if !ok {
				continue
			}

			// grab pechelper.OAISecurity from the stored interface{}
			var sEntry oAISecurity
			switch v := secdefn.(type) {
			case *oAISecurity:
				sEntry = *v
			case oAISecurity:
				sEntry = v
			case string:
				sEntry = oAISecurity{
					Name:   v,
					Scopes: []string{},
				}
			default:
				// not valid type
				log.AppTool.Printf("skipping Security openapi spec for %s:%s, invalid metadata type %v", route.Method, route.Path, v)
				continue
			}

			if _, ok := swo.SecurityDefinitions[sEntry.Name]; !ok {
				log.AppTool.Printf("skipping Security openapi spec for %s:%s, '%s' not found in SecurityDefinitions", route.Method, route.Path, sEntry.Name)
				continue
			}
			routePath := route.Path
			if swo.BasePath != "" {
				routePath = strings.TrimPrefix(routePath, swo.BasePath)
				if !strings.HasPrefix(routePath, "/") {
					routePath = "/" + routePath
				}
			}
			// grab path and path item in openapi spec
			path, err := swo.Paths.JSONLookup(routePath)
			if err != nil {
				log.AppTool.Printf("skipping Security openapi spec for %s:%s, %s", route.Method, route.Path, err.Error())
				continue
			}
			pItem := path.(*spec.PathItem)

			// Update respective path Option based on method
			var pOption *spec.Operation
			switch method := strings.ToLower(route.Method); method {
			case "get":
				pOption = pItem.Get
			case "post":
				pOption = pItem.Post
			case "patch":
				pOption = pItem.Patch
			case "delete":
				pOption = pItem.Delete
			case "put":
				pOption = pItem.Put
			case "head":
				pOption = pItem.Head
			case "options":
				pOption = pItem.Options
			default:
				// unsupported method
				log.AppTool.Printf("skipping Security openapi spec for %s:%s, unsupported method '%s'", route.Method, route.Path, route.Method)
				continue
			}
			// update the pOption with security entry
			pOption.SecuredWith(sEntry.Name, sEntry.Scopes...)
		}
	}

}

type oAISecurity struct {
	Name   string   // SecurityDefinition name
	Scopes []string // Scopes for oauth2
}

func (s *oAISecurity) Valid() error {
	switch s.Name {
	case "oauth2":
		return nil
	case "openIdConnect":
		return nil
	default:
		if len(s.Scopes) > 0 {
			return fmt.Errorf("oai Security scopes for scheme '%s' should be empty", s.Name)
		}
	}
	return nil
}

var json jsoniter.API

func init() {
	json = jsoniter.ConfigFastest
	json.RegisterExtension(&extFloat64{})
}

type extFloat64 struct {
	jsoniter.DummyExtension
}

func (e *extFloat64) DecorateEncoder(typ reflect2.Type, encoder jsoniter.ValEncoder) jsoniter.ValEncoder {
	if typ.String() == "float64" {
		return e
	}
	return encoder
}

func (*extFloat64) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
	f := *((*float64)(ptr))
	if math.IsNaN(f) || math.IsInf(f, 0) { //空或者无穷大小返回0
		f = 0
	}
	ret := strings.TrimRight(fmt.Sprintf("%.3f", f), "0")
	stream.WriteRaw(strings.TrimRight(ret, "."))
}

func (*extFloat64) IsEmpty(ptr unsafe.Pointer) bool {
	return *((*float64)(ptr)) == 0
}

// entityJSONAccess is a EntityReaderWriter for JSON encoding
type entityJSONAccess struct {
	// This is used for setting the Content-Type header when writing
	ContentType string
}

// Read unmarshalls the value from JSON
func (e entityJSONAccess) Read(req *restful.Request, v interface{}) error {
	decoder := json.NewDecoder(req.Request.Body)
	decoder.UseNumber()
	return decoder.Decode(v)
}

// Write marshalls the value to JSON and set the Content-Type Header.
func (e entityJSONAccess) Write(resp *restful.Response, status int, v interface{}) error {
	return writeJSON(resp, status, e.ContentType, v)
}

// write marshalls the value to JSON and set the Content-Type Header.
func writeJSON(resp *restful.Response, status int, contentType string, v interface{}) error {
	if v == nil {
		resp.WriteHeader(status)
		// do not write a nil representation
		return nil
	}
	if restful.PrettyPrintResponses {
		// pretty output must be created and written explicitly
		output, err := json.MarshalIndent(v, "", " ")
		if err != nil {
			return err
		}
		resp.Header().Set(restful.HEADER_ContentType, contentType)
		resp.WriteHeader(status)
		_, err = resp.Write(output)
		return err
	}
	// not-so-pretty
	resp.Header().Set(restful.HEADER_ContentType, contentType)
	resp.WriteHeader(status)
	return json.NewEncoder(resp).Encode(v)
}

type pprofHandler string

func (name pprofHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := strings.TrimPrefix(r.URL.Path, string(name))
	path = strings.TrimLeft(path, "/")
	switch path {
	default:
		pprof.Index(w, r)
	case "cmdline":
		pprof.Cmdline(w, r)
	case "profile":
		pprof.Profile(w, r)
	case "symbol":
		pprof.Symbol(w, r)
	case "trace":
		pprof.Trace(w, r)
	}
}
